RxCache 分析与修改

  • 卫雪峰
  • 15 Minutes
  • May 18, 2019

确认需求

  1. 打开App,请求数据时先检查缓存数据是否需要迁移,如果有缓存数据升级,那么就删除原有的缓存数据。
  2. 如果有缓存数据,判断数据是否过期,如果过期则网络请求,如果没有过期,则返回缓存数据。
  3. 如果网络请求成功,返回数据,同时更新缓存信息
  4. 如果网络请求失败,返回缓存信息。
  5. 如果网络失败,缓存为空,则返回空数据或者抛出异常信息

改动点

  1. 每次读取数据之后都会处理过期数据,不满足要求4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/core/internal/cache/RetrieveRecord.java
<T> Record<T> retrieveRecord(String providerKey, String dynamicKey, String dynamicKeyGroup,
boolean useExpiredDataIfLoaderNotAvailable, Long lifeTime, boolean isEncrypted) {
String composedKey = composeKey(providerKey, dynamicKey, dynamicKeyGroup);

Record<T> record = memory.getIfPresent(composedKey);

if (record != null) {
record.setSource(Source.MEMORY);
} else {
try {
record = persistence.retrieveRecord(composedKey, isEncrypted, encryptKey);
record.setSource(Source.PERSISTENCE);
memory.put(composedKey, record);
} catch (Exception ignore) {
return null;
}
}

record.setLifeTime(lifeTime);

// 删除缓存过期的逻辑
// if (hasRecordExpired.hasRecordExpired(record)) {
// if (!dynamicKeyGroup.isEmpty()) {
// evictRecord.evictRecordMatchingDynamicKeyGroup(providerKey, dynamicKey,
// dynamicKeyGroup);
// } else if (!dynamicKey.isEmpty()) {
// evictRecord.evictRecordsMatchingDynamicKey(providerKey, dynamicKey);
// } else {
// evictRecord.evictRecordsMatchingProviderKey(providerKey);
// }
//
// return useExpiredDataIfLoaderNotAvailable ? record : null;
// }
return record;
}
  1. 在请求前,会判断是否过期,同时处理所有过期数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/core/internal/ProcessorProvidersBehaviour

private Observable<Integer> startProcesses(
io.rx_cache2.internal.migration.DoMigrations doMigrations,
final io.rx_cache2.internal.cache.EvictExpiredRecordsPersistence evictExpiredRecordsPersistence) {
// 处理迁移数据,我们只需要处理迁移就好
Observable<Integer> oProcesses = doMigrations.react().flatMap(new Function<Integer, ObservableSource<Integer>>() {
@Override public ObservableSource<Integer> apply(Integer ignore) throws Exception {
// 删除所有过期数据,注释这段逻辑
return evictExpiredRecordsPersistence.startEvictingExpiredRecords();
}
}).subscribeOn((Schedulers.io())).observeOn(Schedulers.io()).share();

oProcesses.subscribe(new Consumer<Integer>() {
@Override public void accept(Integer ignore) throws Exception {
hasProcessesEnded = true;
}
});

return oProcesses;
}
  1. 在缓存过期之后,网络请求失败的同时,依然会处理过期数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/core/internal/ProcessorProvidersBehaviour
private Observable<Reply> getDataFromLoader(final io.rx_cache2.ConfigProvider configProvider,
final Record record)
...
.onErrorReturn(new Function<Object, Object>() {
@Override public Object apply(Object o) throws Exception {
如果网络错误,会判断这个 provider 的数据是否过期,如果过期,就删除掉
// clearKeyIfNeeded(configProvider);

boolean useExpiredData = configProvider.useExpiredDataIfNotLoaderAvailable() != null ?
configProvider.useExpiredDataIfNotLoaderAvailable()
: useExpiredDataIfLoaderNotAvailable;

if (useExpiredData && record != null) {
return new Reply(record.getData(), record.getSource(), configProvider.isEncrypted());
}

throw new io.rx_cache2.RxCacheException(io.rx_cache2.internal.Locale.NOT_DATA_RETURN_WHEN_CALLING_OBSERVABLE_LOADER
+ " "
+ configProvider.getProviderKey(), (Throwable) o);
}
});
  1. 数据迁移过程的bug, 如果缓存为空
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/internal/migration/DeleteRecordMatchingClassName.java

private boolean evictRecord(io.rx_cache2.internal.Record record) {
没有判断 record 为空的情况。
//if(record == null) return false;
String candidate = record.getDataClassName();

for (Class aClass : classes) {
String className = aClass.getName();
if (className.equals(candidate)) {
return true;
}
}

return false;
}
  1. 一些小改动,小判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<T> Observable<T> getData(final io.rx_cache2.ConfigProvider configProvider) {
Record<Object> record = twoLayersCache.retrieve(configProvider.getProviderKey(), configProvider.getDynamicKey(),
configProvider.getDynamicKeyGroup(), useExpiredDataIfLoaderNotAvailable,
configProvider.getLifeTimeMillis(), configProvider.isEncrypted());

Observable<Reply> replyObservable;

// 判断改为没有过期
//if (record != null && !configProvider.evictProvider().evict()) {
// 判断过期方法在 evictExpiredRecordsPersistence 添加。
if (record != null && !evictExpiredRecordsPersistence.isExpired(record)) {
replyObservable = Observable.just(new Reply(record.getData(), record.getSource(), configProvider.isEncrypted()));
} else {
replyObservable = getDataFromLoader(configProvider, record);
}

return (Observable<T>) replyObservable.map(new Function<Reply, Object>() {
@Override public Object apply(Reply reply) throws Exception {
return ProcessorProvidersBehaviour.this.getReturnType(configProvider, reply);
}
});
}

最后更换依赖

1
2
1. Fork代码,修改代码,创建tag,发布到jitpack.io, 客户端更换依赖就好了
2. implementation 'com.github.weixuefeng.RxCache-1:runtime:1.8.4-3x'

感谢

  1. 你不知道的Retrofit缓存库RxCache JessYan 的介绍。
  2. RxCache GitHub 链接
  3. RxCache源码分析